﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static LightCAD.Three.Constants;
using LightCAD.Three.OpenGL;


namespace LightCAD.Three
{
    public class WebGLTexture
    {

        public GLInt texture;
        public int usedTimes;
    }
    public class WebGLTextures
    {
        private WebGLExtensions extensions;
        private WebGLState state;
        private WebGLProperties properties;
        private WebGLCapabilities capabilities;
        private WebGLUtils utils;
        private WebGLInfo info;

        private bool isWebGL2;
        private int maxTextures;
        private int maxCubemapSize;
        private int maxTextureSize;
        private int maxSamples;
        private int multisampledRTTExt;
        private bool supportsInvalidateFramebuffer;
        private JsObj<object, object> _videoTextures;
        private JsObj<object, JsObj<string, object>> _sources;
        private bool useOffscreenCanvas;

        private int textureUnits;


        private JsObj<int, int> wrappingToGL;

        private JsObj<int, int> filterToGL;

        public WebGLTextures(WebGLExtensions extensions, WebGLState state, WebGLProperties properties, WebGLCapabilities capabilities, WebGLUtils utils, WebGLInfo info)
        {
            this.extensions = extensions;
            this.state = state;
            this.properties = properties;
            this.capabilities = capabilities;
            this.utils = utils;
            this.info = info;

            this.isWebGL2 = capabilities.isWebGL2;
            this.maxTextures = capabilities.maxTextures;
            this.maxCubemapSize = capabilities.maxCubemapSize;
            this.maxTextureSize = capabilities.maxTextureSize;
            this.maxSamples = capabilities.maxSamples;
            this.multisampledRTTExt = extensions.has("WEBGL_multisampled_render_to_texture") ? extensions.get("WEBGL_multisampled_render_to_texture") : 0;
            //this.supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : / OculusBrowser / g.test(navigator.userAgent);
            this.supportsInvalidateFramebuffer = false;

            _videoTextures = new JsObj<object, object>();
            _sources = new JsObj<object, JsObj<string, object>>();

            // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas,
            // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")!
            // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d).

            useOffscreenCanvas = false;

            //try
            //{

            //    useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined'
            //        // eslint-disable-next-line compat/compat
            //        && (new OffscreenCanvas(1, 1).getContext('2d')) !== null;

            //}
            //catch (err)
            //{

            //    // Ignore any errors

            //}
            textureUnits = 0;

            wrappingToGL = new JsObj<int, int>
                {

                    { RepeatWrapping, gl.REPEAT },
                    {ClampToEdgeWrapping, gl.CLAMP_TO_EDGE },
                    {MirroredRepeatWrapping, gl.MIRRORED_REPEAT }
                };

            filterToGL = new JsObj<int, int>
                {
                    {NearestFilter,  gl.NEAREST },
                    {NearestMipmapNearestFilter,  gl.NEAREST_MIPMAP_NEAREST},
                    {NearestMipmapLinearFilter,  gl.NEAREST_MIPMAP_LINEAR},

                    {LinearFilter,  gl.LINEAR},
                    {LinearMipmapNearestFilter,  gl.LINEAR_MIPMAP_NEAREST},
                    {LinearMipmapLinearFilter,  gl.LINEAR_MIPMAP_LINEAR}
                };
        }

        public object createCanvas(int width, int height)
        {

            // Use OffscreenCanvas when available. Specially needed in web workers

            //return useOffscreenCanvas ?
            //    // eslint-disable-next-line compat/compat
            //    new OffscreenCanvas(width, height) : createElementNS('canvas');

            return null;

        }

        public Image resizeImage(Image image, bool needsPowerOfTwo, bool needsNewCanvas, int maxSize)
        {

            double scale = 1;

            // handle case if texture exceeds max size

            if (image.width > maxSize || image.height > maxSize)
            {

                scale = maxSize / JMath.max(image.width, image.height);

            }

            // only perform resize if necessary

            if (scale < 1 || needsPowerOfTwo)
            {

                // only perform resize for certain image types

                //if ((typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
                //  (typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
                // (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {

                //    var floor = needsPowerOfTwo ? MathUtils.floorPowerOfTwo : Math.floor;

                //    var width = floor(scale * image.width);
                //    var height = floor(scale * image.height);

                //    if (_canvas === undefined) _canvas = createCanvas(width, height);

                //    // cube textures can't reuse the same canvas

                //    var canvas = needsNewCanvas ? createCanvas(width, height) : _canvas;

                //    canvas.width = width;
                //    canvas.height = height;

                //    var context = canvas.getContext('2d');
                //    context.drawImage(image, 0, 0, width, height);

                //    console.warn('THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').');

                //    return canvas;

                //} else
                //{

                //    if ('data' in image ) {

                //        console.warn('THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').');

                //    }

                //    return image;

                //}

            }

            return image;

        }

        public bool isPowerOfTwo(IImage image)
        {

            return MathUtils.isPowerOfTwo(image.width) && MathUtils.isPowerOfTwo(image.height);

        }

        public bool textureNeedsPowerOfTwo(Texture texture)
        {

            if (isWebGL2) return false;

            return (texture.wrapS != ClampToEdgeWrapping || texture.wrapT != ClampToEdgeWrapping) ||
                (texture.minFilter != NearestFilter && texture.minFilter != LinearFilter);

        }

        public bool textureNeedsGenerateMipmaps(Texture texture, bool supportsMips)
        {

            return texture.generateMipmaps && supportsMips &&
                texture.minFilter != NearestFilter && texture.minFilter != LinearFilter;

        }

        public void generateMipmap(int target)
        {

            gl.generateMipmap(target);

        }

        public int getInternalFormat(string internalFormatName, int glFormat, int glType, int encoding, bool forceLinearEncoding = false)
        {

            if (isWebGL2 == false) return glFormat;

            if (internalFormatName != null)
            {

                //if (gl[internalFormatName] != undefined) return gl[internalFormatName];

                console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'");

            }

            int internalFormat = glFormat;

            if (glFormat == gl.RED)
            {

                if (glType == gl.FLOAT) internalFormat = gl.R32F;
                if (glType == gl.HALF_FLOAT) internalFormat = gl.R16F;
                if (glType == gl.UNSIGNED_BYTE) internalFormat = gl.R8;

            }

            if (glFormat == gl.RG)
            {

                if (glType == gl.FLOAT) internalFormat = gl.RG32F;
                if (glType == gl.HALF_FLOAT) internalFormat = gl.RG16F;
                if (glType == gl.UNSIGNED_BYTE) internalFormat = gl.RG8;

            }

            if (glFormat == gl.RGBA)
            {

                if (glType == gl.FLOAT) internalFormat = gl.RGBA32F;
                if (glType == gl.HALF_FLOAT) internalFormat = gl.RGBA16F;
                if (glType == gl.UNSIGNED_BYTE) internalFormat = (encoding == sRGBEncoding && forceLinearEncoding == false) ? gl.SRGB8_ALPHA8 : gl.RGBA8;
                if (glType == gl.UNSIGNED_SHORT_4_4_4_4) internalFormat = gl.RGBA4;
                if (glType == gl.UNSIGNED_SHORT_5_5_5_1) internalFormat = gl.RGB5_A1;

            }
            if (glFormat == gl.BGRA)
            {
                if (glType == gl.FLOAT) internalFormat = gl.RGBA32F;
                if (glType == gl.HALF_FLOAT) internalFormat = gl.RGBA16F;
                if (glType == gl.UNSIGNED_BYTE) internalFormat = (encoding == sRGBEncoding && forceLinearEncoding == false) ? gl.SRGB8_ALPHA8 : gl.RGBA8;
                if (glType == gl.UNSIGNED_SHORT_4_4_4_4) internalFormat = gl.RGBA4;
                if (glType == gl.UNSIGNED_SHORT_5_5_5_1) internalFormat = gl.RGB5_A1;
            }
            if (internalFormat == gl.R16F || internalFormat == gl.R32F ||
                internalFormat == gl.RG16F || internalFormat == gl.RG32F ||
                internalFormat == gl.RGBA16F || internalFormat == gl.RGBA32F)
            {

                extensions.get("EXT_color_buffer_float");

            }

            return internalFormat;

        }

        public int getMipLevels(Texture texture, Image image, bool supportsMips)
        {

            if (textureNeedsGenerateMipmaps(texture, supportsMips) || (texture is FramebufferTexture && texture.minFilter != NearestFilter && texture.minFilter != LinearFilter))
            {

                return (int)JMath.log2(JMath.max(image.width, image.height)) + 1;

            }
            else if (texture.mipmaps != null && texture.mipmaps.length > 0)
            {

                // user-defined mipmaps

                return texture.mipmaps.length;

            }
            //else if (texture is CompressedTexture && Array.isArray(texture.image))
            else if (texture is CompressedTexture)
            {
                //return image.mipmaps.length;
                return 1;
            }
            else
            {

                // texture without mipmaps (only base level)

                return 1;

            }

        }

        // Fallback filters for non-power-of-2 textures

        public int filterFallback(int f)
        {

            if (f == NearestFilter || f == NearestMipmapNearestFilter || f == NearestMipmapLinearFilter)
            {

                return gl.NEAREST;

            }

            return gl.LINEAR;

        }

        public void onTextureDispose(EventArgs args)
        {
            var texture = args.target as Texture;

            texture.removeEventListener("dispose", onTextureDispose);

            deallocateTexture(texture);

            if (texture is VideoTexture)
            {

                _videoTextures.delete(texture);

            }

        }

        public void onRenderTargetDispose(EventArgs args)
        {

            var renderTarget = args.target as WebGLRenderTarget;

            renderTarget.removeEventListener("dispose", onRenderTargetDispose);

            deallocateRenderTarget(renderTarget);

        }
        public void deallocateTexture(Texture texture)
        {
            var textureProperties = properties.get(texture);

            if (textureProperties["__webglInit"] == null) return;

            // check if it's necessary to remove the WebGLTexture object

            var source = texture.source;
            var webglTextures = _sources.get(source);

            if (webglTextures != null)
            {

                var webglTexture = webglTextures[(string)textureProperties["__cacheKey"]] as WebGLTexture;
                webglTexture.usedTimes--;

                // the WebGLTexture object is not used anymore, remove it

                if (webglTexture.usedTimes == 0)
                {

                    deleteTexture(texture);

                }

                // remove the weak map entry if no WebGLTexture uses the source anymore

                if (webglTextures.Count == 0)
                {

                    _sources.delete(source);
                    foreach (var data in source.data)
                    {
                        if (data is Image)
                        {
                            var imag = (data as Image);
                            //imag.bmp?.Dispose();
                            properties.remove(texture.source);
                        }
                    }
                    properties.remove(texture.source);
                }

            }

            properties.remove(texture);

            //if (texture.images.Count > 0)
            //{
            //    foreach(var img in texture.images)
            //    {
            //        if(img is Image)
            //        {
            //            var imag = (img as Image);
            //            imag.bmp?.Dispose();
            //            properties.remove(texture.source);
            //        }
            //    }
            //}
        }

        public void deleteTexture(Texture texture)
        {
            var textureProperties = properties.get(texture);
            gl.deleteTexture(((GLInt)textureProperties["__webglTexture"]).Value);

            var source = texture.source;
            var webglTextures = _sources.get(source);
            webglTextures.delete((string)textureProperties["__cacheKey"]);
            info.memory.textures--;
        }

        public void deallocateRenderTarget(WebGLRenderTarget renderTarget)
        {
            var texture = renderTarget.texture;
            var textures = renderTarget.textures;
            var renderTargetProperties = properties.get(renderTarget);
            var textureProperties = renderTarget is WebGLMultipleRenderTargets ? properties.get(textures) : properties.get(texture);

            if (textureProperties["__webglTexture"] != null)
            {

                gl.deleteTexture(((GLInt)textureProperties["__webglTexture"]).Value);

                info.memory.textures--;

            }

            if (renderTarget.depthTexture != null)
            {

                renderTarget.depthTexture.dispose();

            }

            if (renderTarget is WebGLCubeRenderTarget)
            {

                for (int i = 0; i < 6; i++)
                {

                    gl.deleteFramebuffer((renderTargetProperties["__webglFramebuffer"] as JsArr<GLInt>)[i].Value);
                    if (renderTargetProperties["__webglDepthbuffer"] != null) gl.deleteRenderbuffer((renderTargetProperties["__webglDepthbuffer"] as JsArr<GLInt>)[i].Value);

                }

            }
            else
            {
                if (renderTargetProperties["__webglFramebuffer"] != null)
                    gl.deleteFramebuffer(((GLInt)renderTargetProperties["__webglFramebuffer"]).Value);
                if (renderTargetProperties["__webglDepthbuffer"] != null)
                    gl.deleteRenderbuffer(((GLInt)renderTargetProperties["__webglDepthbuffer"]).Value);
                if (renderTargetProperties["__webglMultisampledFramebuffer"] != null)
                    gl.deleteFramebuffer(((GLInt)renderTargetProperties["__webglMultisampledFramebuffer"]).Value);

                if (renderTargetProperties["__webglColorRenderbuffer"] != null)
                {
                    var colorRenderbuffer = renderTargetProperties["__webglColorRenderbuffer"] as JsArr<GLInt>;
                    for (int i = 0; i < colorRenderbuffer.length; i++)
                    {

                        if (colorRenderbuffer[i].Value > 0) gl.deleteRenderbuffer(colorRenderbuffer[i].Value);

                    }

                }

                if (renderTargetProperties["__webglDepthRenderbuffer"] != null) gl.deleteRenderbuffer(((GLInt)renderTargetProperties["__webglDepthRenderbuffer"]).Value);

            }

            if (renderTarget is WebGLMultipleRenderTargets)
            {
                for (int i = 0, il = textures.length; i < il; i++)
                {

                    var attachmentProperties = properties.get(textures[i]);

                    if (attachmentProperties["__webglTexture"] != null)
                    {

                        gl.deleteTexture(((GLInt)(attachmentProperties["__webglTexture"])).Value);

                        info.memory.textures--;

                    }

                    properties.remove(textures[i]);

                }

            }

            properties.remove(texture);
            properties.remove(textures);
            properties.remove(renderTarget);

        }

        public void resetTextureUnits()
        {

            textureUnits = 0;

        }

        public int allocateTextureUnit()
        {

            var textureUnit = textureUnits;

            if (textureUnit >= maxTextures)
            {

                console.warn("THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only " + maxTextures);

            }

            textureUnits += 1;

            return textureUnit;

        }


        public string getTextureCacheKey(Texture texture)
        {

            var array = new JsArr<object>();

            array.push(texture.wrapS);
            array.push(texture.wrapT);
            array.push(texture.GetField("wrapR", 0));
            array.push(texture.magFilter);
            array.push(texture.minFilter);
            array.push(texture.anisotropy);
            array.push(texture.internalFormat);
            array.push(texture.format);
            array.push(texture.type);
            array.push(texture.generateMipmaps);
            array.push(texture.premultiplyAlpha);
            array.push(texture.flipY);
            array.push(texture.unpackAlignment);
            array.push(texture.encoding);

            return array.join(",");

        }


        public void setTexture2D(Texture texture, int slot)
        {

            var textureProperties = properties.get(texture);

            if (texture is VideoTexture) updateVideoTexture(texture as VideoTexture);

            if (texture.isRenderTargetTexture == false && texture.version > 0 && Convert.ToInt32(textureProperties["__version"]) != texture.version)
            {

                var image = texture.image as Image;

                if (image == null)
                {

                    console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");

                }
                else if (image.complete == false)
                {

                    console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete");

                }
                else
                {

                    uploadTexture(textureProperties, texture, slot);
                    return;

                }

            }

            state.bindTexture(gl.TEXTURE_2D, (GLInt)(textureProperties["__webglTexture"]), gl.TEXTURE0 + slot);

        }


        public void setTexture2DArray(Texture texture, int slot)
        {

            var textureProperties = properties.get(texture);

            if (texture.version > 0 && Convert.ToInt32(textureProperties["__version"]) != texture.version)
            {

                uploadTexture(textureProperties, texture, slot);
                return;

            }

            state.bindTexture(gl.TEXTURE_2D_ARRAY, (GLInt)(textureProperties["__webglTexture"]), gl.TEXTURE0 + slot);

        }


        public void setTexture3D(Texture texture, int slot)
        {

            var textureProperties = properties.get(texture);

            if (texture.version > 0 && Convert.ToInt32(textureProperties["__version"]) != texture.version)
            {

                uploadTexture(textureProperties, texture, slot);
                return;

            }

            state.bindTexture(gl.TEXTURE_3D, (GLInt)(textureProperties["__webglTexture"]), gl.TEXTURE0 + slot);

        }


        public void setTextureCube(Texture texture, int slot)
        {

            var textureProperties = properties.get(texture);

            if (texture.version > 0 && Convert.ToInt32(textureProperties["__version"]) != texture.version)
            {

                uploadCubeTexture(textureProperties, texture, slot);
                return;

            }

            state.bindTexture(gl.TEXTURE_CUBE_MAP, (GLInt)(textureProperties["__webglTexture"]), gl.TEXTURE0 + slot);

        }


        public void setTextureParameters(int textureType, Texture texture, bool supportsMips)
        {

            if (supportsMips)
            {
                gl.texParameteri(textureType, gl.TEXTURE_WRAP_S, wrappingToGL[texture.wrapS]);
                gl.texParameteri(textureType, gl.TEXTURE_WRAP_T, wrappingToGL[texture.wrapT]);

                if (textureType == gl.TEXTURE_3D || textureType == gl.TEXTURE_2D_ARRAY)
                {
                    gl.texParameteri(textureType, gl.TEXTURE_WRAP_R, wrappingToGL[(texture as DataArrayTexture).wrapR]);

                }

                gl.texParameteri(textureType, gl.TEXTURE_MAG_FILTER, filterToGL[texture.magFilter]);
                gl.texParameteri(textureType, gl.TEXTURE_MIN_FILTER, filterToGL[texture.minFilter]);

            }
            else
            {

                gl.texParameteri(textureType, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                gl.texParameteri(textureType, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

                if (textureType == gl.TEXTURE_3D || textureType == gl.TEXTURE_2D_ARRAY)
                {

                    gl.texParameteri(textureType, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);

                }

                if (texture.wrapS != ClampToEdgeWrapping || texture.wrapT != ClampToEdgeWrapping)
                {

                    console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.");

                }

                gl.texParameteri(textureType, gl.TEXTURE_MAG_FILTER, filterFallback(texture.magFilter));
                gl.texParameteri(textureType, gl.TEXTURE_MIN_FILTER, filterFallback(texture.minFilter));

                if (texture.minFilter != NearestFilter && texture.minFilter != LinearFilter)
                {

                    console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.");

                }

            }

            if (true)//||extensions.has("EXT_texture_filter_anisotropic"))
            {

                //var extension = extensions.get("EXT_texture_filter_anisotropic");

                if (texture.magFilter == NearestFilter) return;
                if (texture.minFilter != NearestMipmapLinearFilter && texture.minFilter != LinearMipmapLinearFilter) return;
                if (texture.type == FloatType && extensions.has("OES_texture_float_linear") == false) return; // verify extension for WebGL 1 and WebGL 2
                if (isWebGL2 == false && (texture.type == HalfFloatType && extensions.has("OES_texture_half_float_linear") == false)) return; // verify extension for WebGL 1 only

                if (texture.anisotropy > 1 || properties.get(texture)["__currentAnisotropy"] != null)
                {

                    gl.texParameterf(textureType, gl.TEXTURE_MAX_ANISOTROPY, (int)JMath.min(texture.anisotropy, capabilities.getMaxAnisotropy()));
                    properties.get(texture)["__currentAnisotropy"] = texture.anisotropy;

                }

            }

        }

        public bool initTexture(JsObj<object> textureProperties, Texture texture)
        {

            bool forceUpload = false;

            if (textureProperties["__webglInit"] == null)
            {

                textureProperties["__webglInit"] = true;

                texture.addEventListener("dispose", onTextureDispose);

            }

            // create Source <-> WebGLTextures mapping if necessary

            var source = texture.source;
            var webglTextures = _sources.get(source);

            if (webglTextures == null)
            {

                webglTextures = new JsObj<string, object>();
                _sources.set(source, webglTextures);

            }

            // check if there is already a WebGLTexture object for the given texture parameters

            var textureCacheKey = getTextureCacheKey(texture);

            if (textureCacheKey != (string)textureProperties["__cacheKey"])
            {

                // if not, create a new instance of WebGLTexture

                if (webglTextures[textureCacheKey] == null)
                {

                    // create new entry

                    webglTextures[textureCacheKey] = new WebGLTexture()
                    {
                        texture = gl.createTexture(),
                        usedTimes = 0
                    };

                    info.memory.textures++;

                    // when a new instance of WebGLTexture was created, a texture upload is required
                    // even if the image contents are identical

                    forceUpload = true;

                }

                (webglTextures[textureCacheKey] as WebGLTexture).usedTimes++;

                // every time the texture cache key changes, it's necessary to check if an instance of
                // WebGLTexture can be deleted in order to avoid a memory leak.

                var cacheKeyProperties = (string)textureProperties["__cacheKey"];
                var webglTexture = cacheKeyProperties != null ? webglTextures[cacheKeyProperties] as WebGLTexture : null;

                if (webglTexture != null)
                {

                    webglTexture.usedTimes--;

                    if (webglTexture.usedTimes == 0)
                    {

                        deleteTexture(texture);

                    }

                }

                // store references to cache key and WebGLTexture object

                textureProperties["__cacheKey"] = textureCacheKey;
                textureProperties["__webglTexture"] = (webglTextures[textureCacheKey] as WebGLTexture).texture;

            }

            return forceUpload;

        }

        public void uploadTexture(JsObj<object> textureProperties, Texture texture, int slot)
        {

            int textureType = gl.TEXTURE_2D;

            if (texture is DataArrayTexture || texture is CompressedArrayTexture) textureType = gl.TEXTURE_2D_ARRAY;
            if (texture is Data3DTexture) textureType = gl.TEXTURE_3D;

            var forceUpload = initTexture(textureProperties, texture);
            var source = texture.source;

            state.bindTexture(textureType, (GLInt)(textureProperties["__webglTexture"]), gl.TEXTURE0 + slot);

            var sourceProperties = properties.get(source);

            if (source.version != Convert.ToInt32(sourceProperties["__version"]) || forceUpload)
            {

                state.activeTexture(gl.TEXTURE0 + slot);
                //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, texture.flipY);
                //gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha);
                gl.pixelStorei(gl.UNPACK_ALIGNMENT, texture.unpackAlignment);
                //gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);

                bool needsPowerOfTwo = textureNeedsPowerOfTwo(texture) && isPowerOfTwo(texture.image) == false;
                Image image = resizeImage(texture.image as Image, needsPowerOfTwo, false, maxTextureSize);
                image = verifyColorSpace(texture, image);

                var supportsMips = isPowerOfTwo(image) || isWebGL2;
                var glFormat = utils.convert(texture.format, texture.encoding);

                int glType = utils.convert(texture.type),
                    glInternalFormat = getInternalFormat(texture.internalFormat, glFormat, glType, texture.encoding, texture is VideoTexture);

                setTextureParameters(textureType, texture, supportsMips);

                Image mipmap;
                var mipmaps = texture.mipmaps;

                var useTexStorage = (isWebGL2 && texture is VideoTexture != true);
                var allocateMemory = (sourceProperties["__version"] == null) || (forceUpload);
                var levels = getMipLevels(texture, image, supportsMips);

                if (texture is DepthTexture)
                {

                    // populate depth texture with dummy data

                    glInternalFormat = gl.DEPTH_COMPONENT;

                    if (isWebGL2)
                    {

                        if (texture.type == FloatType)
                        {

                            glInternalFormat = gl.DEPTH_COMPONENT32F;

                        }
                        else if (texture.type == UnsignedIntType)
                        {

                            glInternalFormat = gl.DEPTH_COMPONENT24;

                        }
                        else if (texture.type == UnsignedInt248Type)
                        {

                            glInternalFormat = gl.DEPTH24_STENCIL8;

                        }
                        else
                        {

                            glInternalFormat = gl.DEPTH_COMPONENT16; // WebGL2 requires sized internalformat for glTexImage2D

                        }

                    }
                    else
                    {

                        if (texture.type == FloatType)
                        {

                            console.error("WebGLRenderer: Floating point depth texture requires WebGL2.");

                        }

                    }

                    // validation checks for WebGL 1

                    if (texture.format == DepthFormat && glInternalFormat == gl.DEPTH_COMPONENT)
                    {

                        // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are
                        // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT
                        // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
                        if (texture.type != UnsignedShortType && texture.type != UnsignedIntType)
                        {

                            console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.");

                            texture.type = UnsignedIntType;
                            glType = utils.convert(texture.type);

                        }

                    }

                    if (texture.format == DepthStencilFormat && glInternalFormat == gl.DEPTH_COMPONENT)
                    {

                        // Depth stencil textures need the DEPTH_STENCIL internal format
                        // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
                        glInternalFormat = gl.DEPTH_STENCIL;

                        // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are
                        // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL.
                        // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
                        if (texture.type != UnsignedInt248Type)
                        {

                            console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.");

                            texture.type = UnsignedInt248Type;
                            glType = utils.convert(texture.type);

                        }

                    }

                    //

                    if (allocateMemory)
                    {

                        if (useTexStorage)
                        {

                            state.texStorage2D(gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height);

                        }
                        else
                        {

                            state.texImage2D(gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null);

                        }

                    }

                }
                else if (texture is DataTexture)
                {

                    // use manually created mipmaps if available
                    // if there are no manual mipmaps
                    // set 0 level mipmap and then use GL to generate other mipmap levels

                    if (mipmaps.length > 0 && supportsMips)
                    {

                        if (useTexStorage && allocateMemory)
                        {

                            state.texStorage2D(gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[0].width, mipmaps[0].height);

                        }

                        for (int i = 0, il = mipmaps.length; i < il; i++)
                        {

                            mipmap = mipmaps[i];

                            if (useTexStorage)
                            {

                                state.texSubImage2D(gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap);

                            }
                            else
                            {

                                state.texImage2D(gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap);

                            }

                        }

                        texture.generateMipmaps = false;

                    }
                    else
                    {

                        if (useTexStorage)
                        {

                            if (allocateMemory)
                            {

                                state.texStorage2D(gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height);

                            }

                            state.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image);

                        }
                        else
                        {

                            state.texImage2D(gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image);

                        }

                    }

                }
                else if (texture is CompressedTexture)
                {

                    if (texture is CompressedArrayTexture)
                    {

                        if (useTexStorage && allocateMemory)
                        {

                            state.texStorage3D(gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[0].width, mipmaps[0].height, image.depth);

                        }

                        for (int i = 0, il = mipmaps.length; i < il; i++)
                        {

                            mipmap = mipmaps[i];

                            if (texture.format != RGBAFormat)
                            {

                                if (glFormat != 0)
                                {

                                    if (useTexStorage)
                                    {

                                        state.compressedTexSubImage3D(gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data.Length, mipmap);

                                    }
                                    else
                                    {

                                        state.compressedTexImage3D(gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data.Length, mipmap);

                                    }

                                }
                                else
                                {

                                    console.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()");

                                }

                            }
                            else
                            {

                                if (useTexStorage)
                                {

                                    state.texSubImage3D(gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap);

                                }
                                else
                                {

                                    state.texImage3D(gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap);

                                }

                            }

                        }

                    }
                    else
                    {

                        if (useTexStorage && allocateMemory)
                        {

                            state.texStorage2D(gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[0].width, mipmaps[0].height);

                        }

                        for (int i = 0, il = mipmaps.length; i < il; i++)
                        {

                            mipmap = mipmaps[i];

                            if (texture.format != RGBAFormat)
                            {

                                if (glFormat != 0)
                                {

                                    if (useTexStorage)
                                    {

                                        state.compressedTexSubImage2D(gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data.Length, mipmap);

                                    }
                                    else
                                    {

                                        state.compressedTexImage2D(gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data.Length, mipmap);

                                    }

                                }
                                else
                                {

                                    console.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()");

                                }

                            }
                            else
                            {

                                if (useTexStorage)
                                {

                                    state.texSubImage2D(gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap);

                                }
                                else
                                {

                                    state.texImage2D(gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap);

                                }

                            }

                        }

                    }

                }
                else if (texture is DataArrayTexture)
                {

                    if (useTexStorage)
                    {

                        if (allocateMemory)
                        {

                            state.texStorage3D(gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth);

                        }

                        state.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image);

                    }
                    else
                    {

                        state.texImage3D(gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image);

                    }

                }
                else if (texture is Data3DTexture)
                {

                    if (useTexStorage)
                    {

                        if (allocateMemory)
                        {

                            state.texStorage3D(gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth);

                        }

                        state.texSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image);

                    }
                    else
                    {

                        state.texImage3D(gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image);

                    }

                }
                else if (texture is FramebufferTexture)
                {

                    if (allocateMemory)
                    {

                        if (useTexStorage)
                        {

                            state.texStorage2D(gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height);

                        }
                        else
                        {

                            int width = image.width, height = image.height;

                            for (int i = 0; i < levels; i++)
                            {

                                state.texImage2D(gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null);

                                width >>= 1;
                                height >>= 1;

                            }

                        }

                    }

                }
                else
                {

                    // regular Texture (image, video, canvas)

                    // use manually created mipmaps if available
                    // if there are no manual mipmaps
                    // set 0 level mipmap and then use GL to generate other mipmap levels

                    if (mipmaps.length > 0 && supportsMips)
                    {

                        if (useTexStorage && allocateMemory)
                        {

                            state.texStorage2D(gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[0].width, mipmaps[0].height);

                        }

                        for (int i = 0, il = mipmaps.length; i < il; i++)
                        {

                            mipmap = mipmaps[i];

                            if (useTexStorage)
                            {

                                state.texSubImage2D(gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap);

                            }
                            else
                            {

                                state.texImage2D(gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap);

                            }

                        }

                        texture.generateMipmaps = false;

                    }
                    else
                    {

                        if (useTexStorage)
                        {

                            if (allocateMemory)
                            {

                                state.texStorage2D(gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height);

                            }

                            state.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image);

                        }
                        else
                        {

                            state.texImage2D(gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image);

                        }

                    }

                }

                if (textureNeedsGenerateMipmaps(texture, supportsMips))
                {

                    generateMipmap(textureType);

                }

                sourceProperties["__version"] = source.version;

                texture.onUpdate(texture);

            }

            textureProperties["__version"] = texture.version;

        }

        public void uploadCubeTexture(JsObj<object> textureProperties, Texture texture, int slot)
        {

            if (texture.images.length != 6) return;

            var forceUpload = initTexture(textureProperties, texture);
            var source = texture.source;

            state.bindTexture(gl.TEXTURE_CUBE_MAP, (GLInt)(textureProperties["__webglTexture"]), gl.TEXTURE0 + slot);

            var sourceProperties = properties.get(source);

            if (source.version != Convert.ToInt32(sourceProperties["__version"]) || forceUpload)
            {

                state.activeTexture(gl.TEXTURE0 + slot);

                //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, texture.flipY);
                //gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha);
                gl.pixelStorei(gl.UNPACK_ALIGNMENT, texture.unpackAlignment);
                //gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);

                var isCompressed = (texture is CompressedTexture || texture.images[0] is CompressedTexture);
                var isDataTexture = (texture.images[0] != null && texture.images[0] is DataTexture);

                var cubeImage = new JsArr<object>();

                for (int i = 0; i < 6; i++)
                {

                    if (!isCompressed && !isDataTexture)
                    {

                        cubeImage[i] = resizeImage((Image)texture.images[i], false, true, maxCubemapSize);

                    }
                    else
                    {

                        cubeImage[i] = isDataTexture ? (texture.images[i] as DataTexture).image : (Image)texture.images[i];

                    }

                    cubeImage[i] = verifyColorSpace(texture, cubeImage[i] as Image);

                }

                var image = cubeImage[0] as Image;
                var supportsMips = isPowerOfTwo(image) || isWebGL2;
                var glFormat = utils.convert(texture.format, texture.encoding);
                var glType = utils.convert(texture.type);
                var glInternalFormat = getInternalFormat(texture.internalFormat, glFormat, glType, texture.encoding);

                var useTexStorage = (isWebGL2 && texture is VideoTexture);
                var allocateMemory = (sourceProperties["__version"] == null) || (forceUpload);
                int levels = getMipLevels(texture, image, supportsMips);

                setTextureParameters(gl.TEXTURE_CUBE_MAP, texture, supportsMips);

                JsArr<Image> mipmaps;

                if (isCompressed)
                {

                    if (useTexStorage && allocateMemory)
                    {

                        state.texStorage2D(gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height);

                    }

                    for (int i = 0; i < 6; i++)
                    {
                        mipmaps = new JsArr<Image>();
                        mipmaps.AddRange((cubeImage[i] as Texture).mipmaps);

                        for (int j = 0; j < mipmaps.length; j++)
                        {

                            var mipmap = mipmaps[j];

                            if (texture.format != RGBAFormat)
                            {

                                if (glFormat != 0)
                                {

                                    if (useTexStorage)
                                    {

                                        state.compressedTexSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, (mipmap as Image).width, (mipmap as Image).height, glFormat, (mipmap as Image).data.Length, (mipmap as Image));

                                    }
                                    else
                                    {

                                        state.compressedTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, (mipmap as Image).width, (mipmap as Image).height, 0, (mipmap as Image).data.Length, (mipmap as Image));

                                    }

                                }
                                else
                                {

                                    console.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()");

                                }

                            }
                            else
                            {

                                if (useTexStorage)
                                {

                                    state.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, (mipmap as Image).width, (mipmap as Image).height, glFormat, glType, (mipmap as Image));

                                }
                                else
                                {

                                    state.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, (mipmap as Image).width, (mipmap as Image).height, 0, glFormat, glType, (mipmap as Image));

                                }

                            }

                        }

                    }

                }
                else
                {

                    mipmaps = new JsArr<Image>();
                    mipmaps.AddRange(texture.mipmaps);

                    if (useTexStorage && allocateMemory)
                    {

                        // TODO: Uniformly handle mipmap definitions
                        // Normal textures and compressed cube textures define base level + mips with their mipmap array
                        // Uncompressed cube textures use their mipmap array only for mips (no base level)

                        if (mipmaps.length > 0) levels++;

                        state.texStorage2D(gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, (cubeImage[0] as Image).width, (cubeImage[0] as Image).height);

                    }

                    for (int i = 0; i < 6; i++)
                    {

                        if (isDataTexture)
                        {

                            if (useTexStorage)
                            {

                                state.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, (cubeImage[i] as Image).width, (cubeImage[i] as Image).height, glFormat, glType, cubeImage[i] as Image);

                            }
                            else
                            {

                                state.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, (cubeImage[i] as Image).width, (cubeImage[i] as Image).height, 0, glFormat, glType, cubeImage[i] as Image);

                            }

                            for (int j = 0; j < mipmaps.length; j++)
                            {

                                var mipmap = mipmaps[j];
                                //var mipmapImage = ((mipmap as Texture).images[i] as Texture).image;
                                var mipmapImage = mipmap;

                                if (useTexStorage)
                                {

                                    state.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage);

                                }
                                else
                                {

                                    state.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage);

                                }

                            }

                        }
                        else
                        {

                            var cubeImageItem = cubeImage[i] as Image;
                            if (useTexStorage)
                            {

                                state.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImageItem.width, cubeImageItem.height, glFormat, glType, cubeImageItem);

                            }
                            else
                            {

                                state.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImageItem.width, cubeImageItem.height, 0, glFormat, glType, cubeImageItem);

                            }

                            for (int j = 0; j < mipmaps.length; j++)
                            {

                                var mipmap = mipmaps[j];
                                //var mipmapImage = (mipmap as Texture).images[i] as Image;
                                var mipmapImage = mipmap;
                                if (useTexStorage)
                                {

                                    state.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage);

                                }
                                else
                                {

                                    state.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage);

                                }

                            }

                        }

                    }

                }

                if (textureNeedsGenerateMipmaps(texture, supportsMips))
                {

                    // We assume images for cube map have the same size.
                    generateMipmap(gl.TEXTURE_CUBE_MAP);

                }

                sourceProperties["__version"] = source.version;

                texture.onUpdate(texture);

            }

            textureProperties["__version"] = texture.version;

        }

        // Render targets

        // Setup storage for target texture and bind it to correct framebuffer
        public void setupFrameBufferTexture(GLInt framebuffer, WebGLRenderTarget renderTarget, Texture texture, int attachment, int textureTarget)
        {

            var glFormat = utils.convert(texture.format, texture.encoding);
            var glType = utils.convert(texture.type);
            var glInternalFormat = getInternalFormat(texture.internalFormat, glFormat, glType, texture.encoding);
            var renderTargetProperties = properties.get(renderTarget);

            if (!(renderTargetProperties["__hasExternalTextures"] != null))
            {

                if (textureTarget == gl.TEXTURE_3D || textureTarget == gl.TEXTURE_2D_ARRAY)
                {

                    state.texImage3D(textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.depth, 0, glFormat, glType, null);

                }
                else
                {

                    state.texImage2D(textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null);

                }

            }

            state.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

            if (useMultisampledRTT(renderTarget))
            {

                //multisampledRTTExt.framebufferTexture2DMultisampleEXT(gl.FRAMEBUFFER, attachment, textureTarget, properties.get(texture).__webglTexture, 0, getRenderTargetSamples(renderTarget));

            }
            else if (textureTarget == gl.TEXTURE_2D || (textureTarget >= gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= gl.TEXTURE_CUBE_MAP_NEGATIVE_Z))
            { // see #24753

                gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, textureTarget, ((GLInt)(properties.get(texture)["__webglTexture"])).Value, 0);

            }

            state.bindFramebuffer(gl.FRAMEBUFFER, null);

        }


        // Setup storage for internal depth/stencil buffers and bind to correct framebuffer
        public void setupRenderBufferStorage(GLInt renderbuffer, WebGLRenderTarget renderTarget, bool isMultisample)
        {

            gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer.Value);

            if (renderTarget.depthBuffer && !renderTarget.stencilBuffer)
            {

                int glInternalFormat = gl.DEPTH_COMPONENT16;

                if (isMultisample || useMultisampledRTT(renderTarget))
                {

                    var depthTexture = renderTarget.depthTexture;

                    if (depthTexture != null && depthTexture is DepthTexture)
                    {

                        if (depthTexture.type == FloatType)
                        {

                            glInternalFormat = gl.DEPTH_COMPONENT32F;

                        }
                        else if (depthTexture.type == UnsignedIntType)
                        {

                            glInternalFormat = gl.DEPTH_COMPONENT24;

                        }

                    }

                    var samples = getRenderTargetSamples(renderTarget);

                    if (useMultisampledRTT(renderTarget))
                    {

                        //multisampledRTTExt.renderbufferStorageMultisampleEXT(gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height);

                    }
                    else
                    {

                        gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height);

                    }

                }
                else
                {

                    gl.renderbufferStorage(gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height);

                }

                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer.Value);

            }
            else if (renderTarget.depthBuffer && renderTarget.stencilBuffer)
            {

                var samples = getRenderTargetSamples(renderTarget);

                if (isMultisample && useMultisampledRTT(renderTarget) == false)
                {

                    gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height);

                }
                else if (useMultisampledRTT(renderTarget))
                {

                    //multisampledRTTExt.renderbufferStorageMultisampleEXT(gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height);

                }
                else
                {

                    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height);

                }


                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer.Value);

            }
            else
            {

                var textures = renderTarget is WebGLMultipleRenderTargets ? renderTarget.textures : renderTarget.textures;

                for (int i = 0; i < textures.length; i++)
                {

                    var texture = textures[i];

                    var glFormat = utils.convert(texture.format, texture.encoding);
                    var glType = utils.convert(texture.type);
                    var glInternalFormat = getInternalFormat(texture.internalFormat, glFormat, glType, texture.encoding);
                    var samples = getRenderTargetSamples(renderTarget);

                    if (isMultisample && useMultisampledRTT(renderTarget) == false)
                    {

                        gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height);

                    }
                    else if (useMultisampledRTT(renderTarget))
                    {

                        //multisampledRTTExt.renderbufferStorageMultisampleEXT(gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height);

                    }
                    else
                    {

                        gl.renderbufferStorage(gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height);

                    }

                }

            }

            gl.bindRenderbuffer(gl.RENDERBUFFER, 0);

        }


        // Setup resources for a Depth Texture for a FBO (needs an extension)
        public void setupDepthTexture(GLInt framebuffer, WebGLRenderTarget renderTarget)
        {

            bool isCube = (renderTarget != null && renderTarget is WebGLCubeRenderTarget);
            if (isCube) throw new Error("Depth Texture with cube render targets is not supported");

            state.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

            if (!(renderTarget.depthTexture != null && renderTarget.depthTexture is DepthTexture))
            {

                throw new Error("renderTarget.depthTexture must be an instance of THREE.DepthTexture");

            }

            // upload an empty depth texture with framebuffer size
            if (!(properties.get(renderTarget.depthTexture)["__webglTexture"] != null) ||
                    renderTarget.depthTexture.image.width != renderTarget.width ||
                    renderTarget.depthTexture.image.height != renderTarget.height)
            {

                renderTarget.depthTexture.image.width = renderTarget.width;
                renderTarget.depthTexture.image.height = renderTarget.height;
                renderTarget.depthTexture.needsUpdate = true;

            }

            setTexture2D(renderTarget.depthTexture, 0);

            var webglDepthTexture = (properties.get(renderTarget.depthTexture)["__webglTexture"]) as GLInt;
            var samples = getRenderTargetSamples(renderTarget);

            if (renderTarget.depthTexture.format == DepthFormat)
            {

                if (useMultisampledRTT(renderTarget))
                {

                    //multisampledRTTExt.framebufferTexture2DMultisampleEXT(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, webglDepthTexture, 0, samples);

                }
                else
                {

                    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, webglDepthTexture.Value, 0);

                }

            }
            else if (renderTarget.depthTexture.format == DepthStencilFormat)
            {

                if (useMultisampledRTT(renderTarget))
                {

                    //multisampledRTTExt.framebufferTexture2DMultisampleEXT(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, webglDepthTexture, 0, samples);

                }
                else
                {

                    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, webglDepthTexture.Value, 0);

                }

            }
            else
            {

                throw new Error("Unknown depthTexture format");

            }

        }


        // Setup GL resources for a non-texture depth buffer
        public void setupDepthRenderbuffer(WebGLRenderTarget renderTarget)
        {

            var renderTargetProperties = properties.get(renderTarget);
            bool isCube = (renderTarget is WebGLCubeRenderTarget);

            if (renderTarget.depthTexture != null && !(renderTargetProperties["__autoAllocateDepthBuffer"] != null))
            {

                if (isCube) throw new Error("target.depthTexture not supported in Cube render targets");

                setupDepthTexture((GLInt)(renderTargetProperties["__webglFramebuffer"]), renderTarget);

            }
            else
            {

                if (isCube)
                {

                    renderTargetProperties["__webglDepthbuffer"] = new JsArr<GLInt>();

                    for (int i = 0; i < 6; i++)
                    {

                        state.bindFramebuffer(gl.FRAMEBUFFER, (renderTargetProperties["__webglFramebuffer"] as JsArr<GLInt>)[i]);
                        (renderTargetProperties["__webglDepthbuffer"] as JsArr<GLInt>)[i] = gl.createRenderbuffer();
                        setupRenderBufferStorage((renderTargetProperties["__webglDepthbuffer"] as JsArr<GLInt>)[i], renderTarget, false);

                    }

                }
                else
                {

                    state.bindFramebuffer(gl.FRAMEBUFFER, (GLInt)(renderTargetProperties["__webglFramebuffer"]));
                    renderTargetProperties["__webglDepthbuffer"] = gl.createRenderbuffer();
                    setupRenderBufferStorage((GLInt)(renderTargetProperties["__webglDepthbuffer"]), renderTarget, false);

                }

            }

            state.bindFramebuffer(gl.FRAMEBUFFER, null);

        }


        // rebind framebuffer with external textures
        public void rebindTextures(WebGLRenderTarget renderTarget, int? colorTexture, int? depthTexture)
        {

            var renderTargetProperties = properties.get(renderTarget);

            if (colorTexture != null)
            {

                setupFrameBufferTexture((GLInt)(renderTargetProperties["__webglFramebuffer"]), renderTarget, renderTarget.texture, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D);

            }

            if (depthTexture != null)
            {

                setupDepthRenderbuffer(renderTarget);

            }

        }


        // Set up GL resources for the render target
        public void setupRenderTarget(WebGLRenderTarget renderTarget)
        {

            var texture = renderTarget.texture;
            var textures = renderTarget.textures;
            var renderTargetProperties = properties.get(renderTarget);
            var textureProperties = renderTarget is WebGLMultipleRenderTargets ? properties.get(textures) : properties.get(texture);

            renderTarget.addEventListener("dispose", onRenderTargetDispose);

            if (renderTarget is WebGLMultipleRenderTargets != true)
            {

                if (textureProperties["__webglTexture"] == null)
                {

                    textureProperties["__webglTexture"] = gl.createTexture();

                }

                textureProperties["__version"] = texture.version;
                info.memory.textures++;

            }

            var isCube = (renderTarget is WebGLCubeRenderTarget);
            var isMultipleRenderTargets = (renderTarget is WebGLMultipleRenderTargets);
            var supportsMips = isPowerOfTwo(renderTarget.image) || isWebGL2;

            // Setup framebuffer

            if (isCube)
            {

                renderTargetProperties["__webglFramebuffer"] = new JsArr<GLInt>();

                for (int i = 0; i < 6; i++)
                {

                    (renderTargetProperties["__webglFramebuffer"] as JsArr<GLInt>)[i] = gl.createFramebuffer();

                }

            }
            else
            {

                renderTargetProperties["__webglFramebuffer"] = gl.createFramebuffer();

                if (isMultipleRenderTargets)
                {

                    if (capabilities.drawBuffers)
                    {

                        for (int i = 0, il = textures.length; i < il; i++)
                        {

                            var attachmentProperties = properties.get(textures[i]);

                            if (attachmentProperties["__webglTexture"] == null)
                            {

                                attachmentProperties["__webglTexture"] = gl.createTexture();

                                info.memory.textures++;

                            }

                        }

                    }
                    else
                    {

                        console.warn("THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.");

                    }

                }

                if ((isWebGL2 && renderTarget.samples > 0) && useMultisampledRTT(renderTarget) == false)
                {

                    //var textures = isMultipleRenderTargets ? renderTarget.textures : new JsArr<Texture>() { texture };

                    renderTargetProperties["__webglMultisampledFramebuffer"] = gl.createFramebuffer();
                    renderTargetProperties["__webglColorRenderbuffer"] = new JsArr<GLInt>();

                    state.bindFramebuffer(gl.FRAMEBUFFER, (GLInt)(renderTargetProperties["__webglMultisampledFramebuffer"]));

                    for (int i = 0; i < textures.length; i++)
                    {

                        var texturesItem = textures[i];
                        (renderTargetProperties["__webglColorRenderbuffer"] as JsArr<GLInt>)[i] = gl.createRenderbuffer();

                        gl.bindRenderbuffer(gl.RENDERBUFFER, (renderTargetProperties["__webglColorRenderbuffer"] as JsArr<GLInt>)[i].Value);

                        var glFormat = utils.convert(texturesItem.format, texturesItem.encoding);
                        var glType = utils.convert(texturesItem.type);
                        var glInternalFormat = getInternalFormat(texturesItem.internalFormat, glFormat, glType, texturesItem.encoding, renderTarget.isXRRenderTarget);
                        var samples = getRenderTargetSamples(renderTarget);
                        gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height);

                        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, (renderTargetProperties["__webglColorRenderbuffer"] as JsArr<GLInt>)[i].Value);

                    }

                    gl.bindRenderbuffer(gl.RENDERBUFFER, 0);

                    if (renderTarget.depthBuffer)
                    {

                        renderTargetProperties["__webglDepthRenderbuffer"] = gl.createRenderbuffer();
                        setupRenderBufferStorage((GLInt)(renderTargetProperties["__webglDepthRenderbuffer"]), renderTarget, true);

                    }

                    state.bindFramebuffer(gl.FRAMEBUFFER, null);

                }

            }

            // Setup color buffer

            if (isCube)
            {

                state.bindTexture(gl.TEXTURE_CUBE_MAP, (GLInt)(textureProperties["__webglTexture"]), null);
                setTextureParameters(gl.TEXTURE_CUBE_MAP, texture, supportsMips);

                for (int i = 0; i < 6; i++)
                {

                    setupFrameBufferTexture((renderTargetProperties["__webglFramebuffer"] as JsArr<GLInt>)[i], renderTarget, texture, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i);

                }

                if (textureNeedsGenerateMipmaps(texture, supportsMips))
                {

                    generateMipmap(gl.TEXTURE_CUBE_MAP);

                }

                state.unbindTexture();

            }
            else if (isMultipleRenderTargets)
            {

                //var textures = renderTarget.textures;

                for (int i = 0, il = textures.length; i < il; i++)
                {

                    var attachment = textures[i];
                    var attachmentProperties = properties.get(attachment);

                    state.bindTexture(gl.TEXTURE_2D, (GLInt)(attachmentProperties["__webglTexture"]), null);
                    setTextureParameters(gl.TEXTURE_2D, attachment, supportsMips);
                    setupFrameBufferTexture((GLInt)(renderTargetProperties["__webglFramebuffer"]), renderTarget, attachment, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D);

                    if (textureNeedsGenerateMipmaps(attachment, supportsMips))
                    {

                        generateMipmap(gl.TEXTURE_2D);

                    }

                }

                state.unbindTexture();

            }
            else
            {

                int glTextureType = gl.TEXTURE_2D;

                if (renderTarget is WebGL3DRenderTarget || renderTarget is WebGLArrayRenderTarget)
                {

                    if (isWebGL2)
                    {

                        glTextureType = renderTarget is WebGL3DRenderTarget ? gl.TEXTURE_3D : gl.TEXTURE_2D_ARRAY;

                    }
                    else
                    {

                        console.error("THREE.WebGLTextures: THREE.Data3DTexture and THREE.DataArrayTexture only supported with WebGL2.");

                    }

                }

                state.bindTexture(glTextureType, (GLInt)(textureProperties["__webglTexture"]), null);
                setTextureParameters(glTextureType, texture, supportsMips);
                setupFrameBufferTexture((GLInt)(renderTargetProperties["__webglFramebuffer"]), renderTarget, texture, gl.COLOR_ATTACHMENT0, glTextureType);

                if (textureNeedsGenerateMipmaps(texture, supportsMips))
                {

                    generateMipmap(glTextureType);

                }

                state.unbindTexture();

            }

            // Setup depth and stencil buffers

            if (renderTarget.depthBuffer)
            {

                setupDepthRenderbuffer(renderTarget);

            }

        }

        public void updateRenderTargetMipmap(WebGLRenderTarget renderTarget)
        {

            var supportsMips = isPowerOfTwo(renderTarget) || isWebGL2;

            var textures = renderTarget is WebGLMultipleRenderTargets ? renderTarget.textures : new JsArr<Texture> { renderTarget.texture };

            for (int i = 0, il = textures.length; i < il; i++)
            {

                var texture = textures[i];

                if (textureNeedsGenerateMipmaps(texture, supportsMips))
                {

                    var target = renderTarget is WebGLCubeRenderTarget ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
                    var webglTexture = (GLInt)properties.get(texture)["__webglTexture"];

                    state.bindTexture(target, webglTexture, null);
                    generateMipmap(target);
                    state.unbindTexture();

                }

            }

        }

        public void updateMultisampleRenderTarget(WebGLRenderTarget renderTarget)
        {

            if ((isWebGL2 && renderTarget.samples > 0) && useMultisampledRTT(renderTarget) == false)
            {

                var textures = renderTarget is WebGLMultipleRenderTargets ? renderTarget.textures : new JsArr<Texture> { renderTarget.texture };
                var width = renderTarget.width;
                var height = renderTarget.height;
                int mask = gl.COLOR_BUFFER_BIT;
                var invalidationArray = new JsArr<int>();
                var depthStyle = renderTarget.stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
                var renderTargetProperties = properties.get(renderTarget);
                var isMultipleRenderTargets = (renderTarget is WebGLMultipleRenderTargets);

                // If MRT we need to remove FBO attachments
                //如果是MRT，删除帧缓冲的附件
                if (isMultipleRenderTargets)
                {

                    for (int i = 0; i < textures.length; i++)
                    {
                        //删除 gl.COLOR_ATTACHMENT0 + i 上的 渲染缓冲附件
                        state.bindFramebuffer(gl.FRAMEBUFFER, (GLInt)(renderTargetProperties["__webglMultisampledFramebuffer"]));
                        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, 0);
                        //删除帧缓冲里写目标的 纹理缓冲附件（颜色附件类型）
                        state.bindFramebuffer(gl.FRAMEBUFFER, (GLInt)(renderTargetProperties["__webglFramebuffer"]));
                        gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, 0, 0);

                    }

                }

                state.bindFramebuffer(gl.READ_FRAMEBUFFER, (GLInt)(renderTargetProperties["__webglMultisampledFramebuffer"]));
                state.bindFramebuffer(gl.DRAW_FRAMEBUFFER, (GLInt)(renderTargetProperties["__webglFramebuffer"]));

                for (int i = 0; i < textures.length; i++)
                {

                    invalidationArray.push(gl.COLOR_ATTACHMENT0 + i);

                    if (renderTarget.depthBuffer)
                    {

                        invalidationArray.push(depthStyle);

                    }

                    var ignoreDepthValues = (renderTargetProperties["__ignoreDepthValues"] != null) ? (bool)renderTargetProperties["__ignoreDepthValues"] : false;

                    if (ignoreDepthValues == false)
                    {

                        if (renderTarget.depthBuffer) mask |= gl.DEPTH_BUFFER_BIT;
                        if (renderTarget.stencilBuffer) mask |= gl.STENCIL_BUFFER_BIT;

                    }

                    if (isMultipleRenderTargets)
                    {

                        gl.framebufferRenderbuffer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, (renderTargetProperties["__webglColorRenderbuffer"] as JsArr<GLInt>)[i].Value);

                    }

                    if (ignoreDepthValues)
                    {
                        var attrs = new int[] { depthStyle };
                        gl.invalidateFramebuffer(gl.READ_FRAMEBUFFER, attrs.Length, attrs);
                        gl.invalidateFramebuffer(gl.DRAW_FRAMEBUFFER, attrs.Length, attrs);

                    }

                    if (isMultipleRenderTargets)
                    {

                        var webglTexture = (GLInt)properties.get(textures[i])["__webglTexture"];
                        gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, webglTexture.Value, 0);

                    }

                    gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, mask, gl.NEAREST);
                    var err = gl.getError();
                    if (err != gl.NO_ERROR)
                    {
                        Console.WriteLine(err);
                    }
                    if (supportsInvalidateFramebuffer)
                    {
                        gl.invalidateFramebuffer(gl.READ_FRAMEBUFFER, invalidationArray.length, invalidationArray.ToArray());

                    }


                }

                state.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
                state.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);

                // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments
                if (isMultipleRenderTargets)
                {

                    for (int i = 0; i < textures.length; i++)
                    {

                        state.bindFramebuffer(gl.FRAMEBUFFER, (GLInt)(renderTargetProperties["__webglMultisampledFramebuffer"]));
                        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, (renderTargetProperties["__webglColorRenderbuffer"] as JsArr<GLInt>)[i].Value);

                        var webglTexture = (GLInt)properties.get(textures[i])["__webglTexture"];

                        state.bindFramebuffer(gl.FRAMEBUFFER, (GLInt)(renderTargetProperties["__webglFramebuffer"]));
                        gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, webglTexture.Value, 0);

                    }

                }

                state.bindFramebuffer(gl.DRAW_FRAMEBUFFER, (GLInt)(renderTargetProperties["__webglMultisampledFramebuffer"]));

            }

        }

        public int getRenderTargetSamples(WebGLRenderTarget renderTarget)
        {

            return JMath.min(maxSamples, renderTarget.samples);

        }

        public bool useMultisampledRTT(WebGLRenderTarget renderTarget)
        {

            var renderTargetProperties = properties.get(renderTarget);

            return isWebGL2 && renderTarget.samples > 0 && extensions.has("WEBGL_multisampled_render_to_texture") && ((bool?)renderTargetProperties["__useRenderToTexture"] != false);

        }

        public void updateVideoTexture(VideoTexture texture)
        {

            var frame = info.render.frame;

            // Check the last frame we updated the VideoTexture

            if (Convert.ToInt32(_videoTextures.get(texture)) != frame)
            {

                _videoTextures.set(texture, frame);
                texture.update();

            }

        }

        public Image verifyColorSpace(Texture texture, Image image)
        {

            var encoding = texture.encoding;
            var format = texture.format;
            var type = texture.type;

            if (texture is CompressedTexture || texture is VideoTexture || texture.format == _SRGBAFormat) return image;

            if (encoding != LinearEncoding)
            {

                // sRGB

                if (encoding == sRGBEncoding)
                {

                    if (!isWebGL2)
                    {

                        // in WebGL 1, try to use EXT_sRGB extension and unsized formats

                        if (extensions.has("EXT_sRGB") && format == RGBAFormat)
                        {

                            texture.format = _SRGBAFormat;

                            // it"s not possible to generate mips in WebGL 1 with this extension

                            texture.minFilter = LinearFilter;
                            texture.generateMipmaps = false;

                        }
                        else
                        {

                            // slow fallback (CPU decode)

                            image = ImageUtils.sRGBToLinear(image);

                        }

                    }
                    else
                    {

                        // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format

                        if ((format != RGBAFormat && format != BGRAFormat) || type != UnsignedByteType)
                        {

                            console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.");

                        }

                    }

                }
                else
                {

                    console.error("THREE.WebGLTextures: Unsupported texture encoding:", encoding);

                }

            }

            return image;

        }
    }
}
